package com.google.code.dnaep;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import com.google.appengine.api.datastore.DatastoreService;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.PreparedQuery;

public class EntityKindHelper {

	private static final Map<String, KindMapping> kindMappings = Collections.synchronizedMap(new HashMap<String, KindMapping>());
	
	static {
		updateKindMappingsFromDatastore();
	}
	
	private static class KindMapping {
		private final Class<?> kindClass;

		public KindMapping(Class<?> kindClass) {
	        this.kindClass = kindClass;
        }
		
		public Class<?> getKindClass() {
        	return kindClass;
        }
		
		public String getKindName() {
			return kindClass.getSimpleName();
		}

		public int hashCode() {
	        return kindClass.hashCode();
        }

		public boolean equals(Object obj) {
	        return kindClass.equals(obj);
        }
		
	}

	public static Class<?> getKindClass(String kindName) {
		return getKindMapping(kindName).getKindClass();
	}

	public static String getKindName(Class<?> kindClass) {
		return getKindMapping(kindClass).getKindName();
	}

	private static KindMapping getKindMapping(String kindName) {
		KindMapping mapping = kindMappings.get(kindName);
		if (mapping != null) {
			return mapping;
		} else {
			return updateKindMapping(kindName);
		}
	}
	
	private static KindMapping getKindMapping(Class<?> kindClass) {
		if (kindClass.getAnnotation(javax.jdo.annotations.PersistenceCapable.class) == null) {
			throw new RuntimeException("Class is not PersistenceCapable" + kindClass);
		}
		KindMapping mapping = kindMappings.get(kindClass.getSimpleName());
		if (mapping != null) {
			return mapping;
		} else {
			return updateKindMapping(kindClass);
		}
	}
	
	private static synchronized KindMapping updateKindMapping(String kindName) {
		Class<?> foundClass = null;
		Package[] packages = Package.getPackages();
		for (Package pack : packages) {
			Class<?> tempClass;
			try {
				tempClass = Class.forName(pack.getName() + "." + kindName);
			} catch (ClassNotFoundException e) {
				continue;
			}
			if (tempClass.getAnnotation(javax.jdo.annotations.PersistenceCapable.class) == null) {
				continue;
			}
			if (foundClass != null) {
				throw new RuntimeException("Duplicate classes found for kind: " + kindName);
			}
			foundClass = tempClass;
		}
		if (foundClass == null) {
			throw new RuntimeException("No class found for kind: " + kindName);
		}
		return updateKindMapping(foundClass);
	}
	
	private static synchronized KindMapping updateKindMapping(Class<?> kindClass) {
		if (kindClass.getAnnotation(javax.jdo.annotations.PersistenceCapable.class) == null) {
			throw new RuntimeException("Class is not PersistenceCapable" + kindClass);
		}
		synchronized (kindMappings) {
			KindMapping mapping = kindMappings.get(kindClass.getSimpleName());
	        if (mapping == null) {
	    		mapping = new KindMapping(kindClass);
	    		kindMappings.put(mapping.getKindName(), mapping);
	        }
    		return mapping;
        }
	}

	public static void updateKindMappingsFromDatastore() {
		DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();
		PreparedQuery global = datastore.prepare(new com.google.appengine.api.datastore.Query(com.google.appengine.api.datastore.Query.KIND_METADATA_KIND));

		for (Entity globalStat : global.asIterable()) {
			String kindName = globalStat.getKey().getName();
			updateKindMapping(kindName);
		}
	}

	public static Collection<Class> getSubclasses(String kindName) {
		Class<?> kindClass = getKindClass(kindName);
		return getSubclasses(kindClass);
	}

	public static Collection<Class> getSubclasses(Class<?> kindClass) {
		Collection<Class> subclasses = new ArrayList<Class>();
		for (KindMapping mapping : kindMappings.values()) {
	        if (kindClass.isAssignableFrom(mapping.getKindClass())) {
	        	subclasses.add(mapping.getKindClass());
	        }
		}
		return subclasses;
	}

}
